package com.dragon.framework.aspect;

import com.dragon.common.utils.ServletUtils;
import com.dragon.common.utils.ip.IpUtils;
import com.dragon.common.utils.time.DateTimePattern;
import com.dragon.framework.json.serializer.MultipartFileSerializer;
import com.dragon.framework.json.serializer.ServletRequestSerializer;
import com.dragon.framework.json.serializer.ServletResponseSerializer;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 系统日志切面
 *
 * @author dragon
 * @version 1.0
 * @date 2024/7/17 15:42
 */
@Aspect
@Component
@ConditionalOnClass(ObjectMapper.class)
public class SystemLogAspect {

    @Resource
    private ObjectMapper objectMapper;


    private static final Logger log = LoggerFactory.getLogger(SystemLogAspect.class);

    /**
     * 单例模式 for ObjectMapper
     */
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    /**
     * jackson过滤器ID
     */
    public static final String JSON_FILTER_ID = "jacksonFieldFilter";

    /**
     * 排除敏感属性字段
     */
    public static final Set<String> EXCLUDE_PROPERTIES = Set.of("password", "oldPassword", "newPassword", "confirmPassword");

    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREAD_LOCAL = new NamedThreadLocal<>("SystemLogAspect jackson Cost Time");

    /**
     * 敏感字段过滤器
     */
    private static final SimpleFilterProvider FILTER_PROVIDER = new SimpleFilterProvider().addFilter(JSON_FILTER_ID, SimpleBeanPropertyFilter.serializeAllExcept(EXCLUDE_PROPERTIES));

    static {
        // 创建一个SimpleModule并添加自定义序列化器
        SimpleModule module = new SimpleModule();
        // MultipartFile序列化器
        module.addSerializer(MultipartFile.class, new MultipartFileSerializer());
        // ServletRequest序列化器
        module.addSerializer(ServletRequest.class, new ServletRequestSerializer());
        // ServletResponse序列化器
        module.addSerializer(ServletResponse.class, new ServletResponseSerializer());
        // 注册模块到ObjectMapper
        OBJECT_MAPPER.registerModule(module);

        JavaTimeModule javaTimeModule = new JavaTimeModule();

        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DateTimePattern.DATETIME_PATTERN)));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DateTimePattern.DATETIME_PATTERN)));

        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DateTimePattern.DATE_PATTERN)));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DateTimePattern.DATE_PATTERN)));

        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DateTimePattern.TIME_PATTERN)));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DateTimePattern.TIME_PATTERN)));

        OBJECT_MAPPER.registerModule(javaTimeModule);

        // 设置过滤器
        OBJECT_MAPPER.setFilterProvider(FILTER_PROVIDER)
            // 定义一个“混入”接口或抽象类，其中包含Jackson注解，然后将这个“混入”指定给目标类使用。
            // 通过ObjectMapper的addMixIn方法，你可以将这些自定义的序列化或反序列化规则“混合”进目标类中
            // 例如，假设你有一个第三方库提供的类ThirdPartyClass，你无法修改它的源代码，但你想在序列化时忽略它的某个字段。你可以这样做
            .addMixIn(Object.class, FieldFilterMixIn.class);
    }


    /**
     * 匹配以 com.dragon.admin 开头的任何包名下后缀为 controller 的类的所有方法
     */
    @Pointcut("execution(* com.dragon..*Controller.*(..))")
    public void pointcutController() {
    }

    /**
     * 处理请求前执行
     */
    @Before(value = "pointcutController()")
    public void boBefore(JoinPoint joinPoint) {
        TIME_THREAD_LOCAL.set(System.currentTimeMillis());
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "pointcutController()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        handleLog(joinPoint, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "pointcutController()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }

    protected void handleLog(final JoinPoint joinPoint, final Exception exception, Object jsonResult) {
        try {
            // 请求ip
            String ip = IpUtils.getIpAddr();
            // 请求方式
            String requestMethod = ServletUtils.getRequest().getMethod();
            // 请求方法
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            // 请求URL
            String queryString = ServletUtils.getRequest().getQueryString();
            String requestUrl = ServletUtils.getRequest().getRequestURI() +
                (StringUtils.isBlank(queryString) ? "" : "?" + queryString);

            // ObjectMapper objectMapper = new ObjectMapper();
            // // 创建一个SimpleModule并添加自定义序列化器
            // SimpleModule module = new SimpleModule();
            // // MultipartFile序列化器
            // module.addSerializer(MultipartFile.class, new MultipartFileSerializer());
            // // ServletRequest序列化器
            // module.addSerializer(ServletRequest.class, new ServletRequestSerializer());
            // // 注册模块到ObjectMapper
            // objectMapper.registerModule(module);
            // // 敏感字段过滤器
            // SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter(JSON_FILTER_ID, SimpleBeanPropertyFilter.serializeAllExcept(EXCLUDE_PROPERTIES));

            // 请求参数
            String args = OBJECT_MAPPER
                // // 设置过滤器
                // .setFilterProvider(FILTER_PROVIDER)
                // // 定义一个“混入”接口或抽象类，其中包含Jackson注解，然后将这个“混入”指定给目标类使用。
                // // 通过ObjectMapper的addMixIn方法，你可以将这些自定义的序列化或反序列化规则“混合”进目标类中
                // // 例如，假设你有一个第三方库提供的类ThirdPartyClass，你无法修改它的源代码，但你想在序列化时忽略它的某个字段。你可以这样做
                // .addMixIn(Object.class, FieldFilterMixIn.class)
                .writeValueAsString(getJoinPointArgs(joinPoint));
            // 消耗时间
            long wasteTime = System.currentTimeMillis() - TIME_THREAD_LOCAL.get();
            // 异常信息
            String exceptionMsg = Objects.isNull(exception) ? "" : exception.getMessage();

            log.warn("SystemLogAspect 请求ip {} 请求方式 {} 请求URL {} 请求方法 {} 消耗时间 {}(ms)",
                ip,
                requestMethod,
                requestUrl,
                className + "." + methodName + "()",
                wasteTime
            );
            log.warn("SystemLogAspect 请求参数 {} 响应结果 {}",
                args,
                objectMapper.writeValueAsString(jsonResult)
            );
            if (exception != null) {
                log.warn("SystemLogAspect 异常信息 {}", exceptionMsg);
            }

        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("SystemLogAspect 异常信息 {}", exp.getMessage(), exp);
        } finally {
            TIME_THREAD_LOCAL.remove();
        }
    }

    /**
     * 获取请求参数
     *
     * @param joinPoint joinPoint
     * @return 请求参数
     */
    private Map<String, Object> getJoinPointArgs(JoinPoint joinPoint) {
        Map<String, Object> paramMap = new HashMap<>(16);

        // 参数名称
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        // 参数值
        Object[] paramValues = joinPoint.getArgs();

        for (int i = 0; i < paramNames.length; i++) {
            try {
                String paramName = paramNames[i];
                Object paramValue = paramValues[i];

                paramMap.put(paramName, paramValue);
            } catch (Exception e) {
                log.error("SystemLogAspect 获取请求参数异常 {}", e.getMessage(), e);
            }
        }

        return paramMap;
    }

    /**
     * 定义一个类或接口用户给过滤器使用
     */
    @JsonFilter(JSON_FILTER_ID)
    interface FieldFilterMixIn {
    }

}
